package cyb;

import cn.hutool.core.map.MapUtil;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.util.*;

/**
 * JoinLess 缺点：
 * 1.要生成大量候选星型实例（从第一个点的邻居集获取），如果星型实例大多都不是团的话，效率会很慢 =》TODO 这部分操作的优化
 *
 * ================================================================================================
 * 步骤：
 * 1.获取1-co，生成2-co
 * 2.根据n阶模式生成n+1阶候选模式
 * 3.根据n+1阶候选模式的星型邻居  计算支持度，筛除一部分模式
 * 4.根据n+1阶候选模式的星型邻居  生成候选实例，计算是否成团,筛除一部分模式
 * 5.根据n+1阶候选模式的团实例   计算支持度，筛除一部分模式
 *
 * 重复2-5直至获得目标阶数模式
 */
public class JoinLess {
    //递归获取a1的所有星型实例
    public static void findStarInstance(List<String[]> list, int i, String str, List<String> result) {
        //当前获取的第i个特征实例
        if (i > list.size() - 1) {
            result.add(str);
            return;
        }
        String[] arr = list.get(i);
        for (String st : arr) {
            if (!str.equals("")) {
                st = str + "," + st;
            }
            findStarInstance(list, i + 1, st, result);
        }
    }

    /**
     * 检查某个 候选表实例，是否成团
     */
    public static boolean checkCliqueInstance(String candidateInstance, Map<String, POI> poiMap) {
        String[] instanceArr = candidateInstance.split(",");
        Integer jie = instanceArr.length;
        for (int i = 0; i < jie; i++) {
            POI poi1 = poiMap.get(instanceArr[i]);
            for (int j = 0; j < jie; j++) {
                if (i < j) {
                    POI poi2 = poiMap.get(instanceArr[j]);
                    if (Util.getDistance(poi1.getX(), poi1.getY(), poi2.getX(), poi2.getY()) > Properties.R) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * 对 nCoMap 根据支持度剪枝
     *
     * @param nCoMap key：n阶模式   value：n阶模式实例集
     */
    public static void cut(Map<String, List<String>> nCoMap, Map<String, List<String>> oneCoMap,
                           Map<String, POI> poiMap) {
        List<String> removeCoList = new LinkedList<>();
        int jie = 0;
        for (Map.Entry<String, List<String>> nCoEntry : nCoMap.entrySet()) {
            String nCoType = nCoEntry.getKey();
            String[] nCoTypeArr = nCoType.split(",");
            if (jie == 0) jie = nCoTypeArr.length;
            Map<String, Set<String>> typeMap = new HashMap<>();//记录当前n+1阶模式下，各特征 实例集
            for (String type : nCoTypeArr) {
                typeMap.put(type, new HashSet<>());
            }

            List<String> nCoTypeList = nCoEntry.getValue();
            for (String nCoStr : nCoTypeList) {
                String[] nCoArr = nCoStr.split(",");
                for (String id : nCoArr) {
                    POI poi = poiMap.get(id);
                    typeMap.get(poi.getType()).add(id);
                }
            }
            //每个n阶模式都要计算支持度，剪枝
            double minCoSupport = 2;
            StringBuffer ss = new StringBuffer();
            for (Map.Entry<String, Set<String>> entry : typeMap.entrySet()) {
                String type = entry.getKey();
                double tempSupport = Util.int2double(typeMap.get(type).size()) / oneCoMap.get(type).size();
                ss.append(tempSupport + ",");
                if (tempSupport < minCoSupport) {
                    minCoSupport = tempSupport;
                }
            }
            if (minCoSupport < Properties.support) {
                if (jie==3){
                    System.out.println(nCoType + "的支持度" + minCoSupport + " < 阈值：" + Properties.support);
                }
                removeCoList.add(nCoType);
            }
        }
        for (String removeCo : removeCoList) {
            nCoMap.remove(removeCo);
        }
        System.out.println("=> " + jie + "阶模式剪枝 " + removeCoList.size() + " 个");
    }


    /**
     * 寻找n阶模式下 实例集的公共邻居集
     *
     * @param startPoiList 实例集 如 a1,b1,c2
     * @param starTypeStr     模式 如 A,B,C
     * @param neighborMap
     * @param poiMap
     */
    public static void findSharingNeighborSet(List<String> startPoiList, String starTypeStr, Map<String, String> neighborMap,
                                              Map<String, POI> poiMap, Map<String, List<String>> nPlusCoMap) {
        int jie = starTypeStr.split(",").length;
        //1阶升2阶 和base算法不一样，直接从每个点的邻居集找，生成2阶模式,不粗筛
        if (jie == 1) {
            for (String starId : startPoiList) { // 类似 a1 a2 a3 a4
                POI starPoi = poiMap.get(starId);
                if (neighborMap.get(starId) != null) {
                    String[] neighborsIdArr = neighborMap.get(starId).split(",");
                    for (String neighborId : neighborsIdArr) {
                        POI neighborPoi = poiMap.get(neighborId);
                        String neighborType = neighborPoi.getType();
//                        if (Integer.valueOf(starTypeStr) < Integer.valueOf(neighborType)) {
                        if (Util.getEarthDistance(starPoi.getXY(), neighborPoi.getXY()) <= Properties.R) {
                            Util.coMapAdd(nPlusCoMap, starTypeStr + "," + neighborType, starId + "," + neighborId);
                        }
//                        }
                    }
                }
            }
        } else {
            for (String starStr : startPoiList) { //类似 a1,b2,c3  a1,b3,c4
                String[] starArr = starStr.split(",");
                Map<String, Integer> neighborNumMap = new HashMap<>();
                for (String starId : starArr) {
                    String[] neighborsIdArr = neighborMap.get(starId).split(",");
                    for (String neighbor : neighborsIdArr) {
                        Integer num = neighborNumMap.get(neighbor);
                        if (num == null) {
                            neighborNumMap.put(neighbor, 1);
                        } else {
                            neighborNumMap.put(neighbor, num + 1);
                        }
                    }
                }
                for (Map.Entry<String, Integer> entry : neighborNumMap.entrySet()) {
                    boolean isSharing = true;

                    if (entry.getValue() != jie) { //和某个点不相邻
                        isSharing = false;
                    }
                    String neighborId = entry.getKey();
                    POI sharingNeighbor = poiMap.get(neighborId);
                    String neighborType = sharingNeighbor.getType();
                    String[] starTypeArr = starTypeStr.split(",");
                    for (String starType : starTypeArr) {
                        if (Integer.valueOf(starType) >= Integer.valueOf(neighborType)) { //新加入的公共邻居点类型必须比n阶模式里的都大
                            isSharing = false;
                            break;
                        }
                    }
                    if (isSharing) {
                        Util.coMapAdd(nPlusCoMap, starTypeStr + "," + neighborType, starStr + "," + neighborId);
                    }
                }
            }
        }

    }

    /**
     * 2个n阶模式合成的 n+1阶候选集 初筛
     * 注意： nCoType1和nCoType2前n-1项相同，最后一项特征左边小于右边
     */
    public static boolean computCanSupport(String nCoType1, String nCoType2, Map<String, String> supportMap) {
        String supportStr1 = supportMap.get(nCoType1);
        String supportStr2 = supportMap.get(nCoType2);

        double minSupport = 2;
        String[] supportArr1 = supportStr1.split(",");
        String[] supportArr2 = supportStr2.split(",");
        if (supportArr1.length < 2) {
            return true; //计算2阶及以上模式的 初筛
        }
        //前n-1相同项，选择最小的参与度
        StringBuffer nPlusCanSupport = new StringBuffer();
        for (int i = 0; i < supportArr1.length; i++) {
            Double support1 = Double.valueOf(supportArr1[i]);
            Double support2 = Double.valueOf(supportArr2[i]);
            nPlusCanSupport.append(Math.min(support1, support2) + ",");
            if (Math.min(support1, support2) < minSupport) {
                minSupport = Math.min(support1, support2);
            }

            if (i == supportArr1.length - 1) {
                nPlusCanSupport.append(support1 + ",");
                if (support1 < minSupport) {
                    minSupport = support1;
                }
                nPlusCanSupport.append(support2 + ",");
                if (support2 < minSupport) {
                    minSupport = support2;
                }
            }
        }
        //n+1阶候选集 初筛，小于 参与度阈值 筛除
        if (minSupport < Properties.support) {
            return true;
        }
        String nPlusCoType = supportStr1 + "," + supportArr2[supportArr2.length - 1];
        supportMap.put(nPlusCoType, nPlusCanSupport.toString());
        return false;
    }

    /**
     * 对n+1阶模式 候选集 初筛
     */
    public static boolean computCanSupportByNeighborMap(String nPlusCoType, Map<String, List<String>> oneCoMap, Map<String, Map<String, String>> neighborMap) {
        Map<String, Set<String>> candidateSetMap = new HashMap<>();//模式的星型实例集
        String[] nPlusCoTypeArr = nPlusCoType.split(",");
        int jie = nPlusCoTypeArr.length;
        List<String> starList = oneCoMap.get(nPlusCoTypeArr[0]);
        for (String starId : starList) {//找第一个特征的每个实例的 星型实例
            Map<String, String> neighborTypeMap = neighborMap.get(starId);
            if (neighborTypeMap != null) {
                for (int j = 1; j < jie; j++) {
                    String neighborType = nPlusCoTypeArr[j];
                    Set<String> candidateSet = candidateSetMap.get(neighborType);
                    if (candidateSet == null) {
                        candidateSet = new HashSet<>();
                    }

                    String neighborTypeList = neighborTypeMap.get(neighborType);
                    if (neighborTypeList != null) {
                        for (String neighborId : neighborTypeList.split(",")) {
                            candidateSet.add(neighborId);
                        }
                        if (candidateSet.size() != 0) {
                            candidateSetMap.put(neighborType, candidateSet);
                        }
                    }
                }
            }
        }
        //某个星型邻居特征的候选集为空
        if (candidateSetMap.size() != jie) {
            return false;
        }
        double minSupport = 2;
        for (Map.Entry<String, Set<String>> entry : candidateSetMap.entrySet()) {
            String type = entry.getKey();
            Set<String> typeSet = entry.getValue();
            double support = typeSet.size() / oneCoMap.get(type).size();
            if (support < minSupport) {
                minSupport = support;
            }
        }
        if (minSupport < Properties.support) {
            return true;
        }
        return false;
    }


    public static Map<String, List<String>> genAll2Co(Map<String, List<String>> oneCoMap, Map<String, POI> poiMap,
                                                      Map<String, Map<String, String>> neighborMap) {
        Map<String, List<String>> twoCoMap = new HashMap<>();
        for (Map.Entry<String, List<String>> starTypeEntry : oneCoMap.entrySet()) {
            List<String> starTypePoiList = starTypeEntry.getValue();
            for (String starId : starTypePoiList) { // 类似 a1 a2 a3 a4
                POI starPoi = poiMap.get(starId);
                String starType = starPoi.getType();
                Map<String, String> neighborTypeMap = neighborMap.get(starId);
                if (neighborTypeMap != null) {
                    //每个点的邻居集
                    for (Map.Entry<String, String> entry : neighborTypeMap.entrySet()) {
                        String neighborType = entry.getKey();
                        String[] neighborsIdArr = entry.getValue().split(",");
                        for (String neighborId : neighborsIdArr) {
                            Util.coMapAdd(twoCoMap, starType + "," + neighborType, starId + "," + neighborId);
//                                        POI neighborPoi = poiMap.get(neighborId);
//                                        String neighborType = neighborPoi.getType();
//                                        if (Integer.valueOf(starTypeStr) < Integer.valueOf(neighborType)) {
//                                            if (Util.getEarthDistance(starPoi.getXY(), neighborPoi.getXY()) <= Properties.R) {
//                                                Util.coMapAdd(nPlusCoMap, starTypeStr + "," + neighborType, starId + "," + neighborId);
//                                            }
//                                        }
                        }
                    }
                }
            }
        }

        return twoCoMap;
    }

    public static void genNPlusCo(Map<String, List<String>> oneCoMap, Map<String, POI> poiMap, Map<String, Map<String, String>> neighborMap,
                                  Map<String, List<String>> nPlusCoMap, String nPlusCoType) {
        int nPlusJie = nPlusCoType.split(",").length;
        //1.通过星型邻居集，计算当前阶候选集参与度，粗筛
        boolean filter = JoinLess.computCanSupportByNeighborMap(nPlusCoType, oneCoMap, neighborMap);
        if (!filter) { //2.初筛通过，进行团检测
            List<String> nPlusCoList = new LinkedList<>();
            String[] nPlusCoTypeArr = nPlusCoType.split(",");
            List<String> starList = oneCoMap.get(nPlusCoTypeArr[0]);
            for (String starId : starList) {//找第一个特征的每个实例的 星型实例
                Map<String, String> neighborTypeMap = neighborMap.get(starId);
                if (MapUtil.isNotEmpty(neighborTypeMap)) {
                    boolean isCanClique = true;
                    List<String[]> neighborList = new LinkedList<>();
                    neighborList.add(new String[]{starId});
                    for (int j = 1; j < nPlusJie; j++) {
                        String neighborType = nPlusCoTypeArr[j];
                        String neighborTypeList = neighborTypeMap.get(neighborType);
                        if (neighborTypeList == null || neighborTypeList.equals("")) {
                            isCanClique = false;
                            break; //星型实例的某个特征为空，则该星型实例不成团
                        } else {
                            neighborList.add(neighborTypeList.split(","));
                        }
                    }
                    if (isCanClique) {
                        List<String> starInstances = new LinkedList<>();
                        //2.1生成星型实例
                        JoinLess.findStarInstance(neighborList, 0, "", starInstances);
                        //2.2检测成团
                        for (String starInstance : starInstances) {
                            boolean isClique = checkCliqueInstance(starInstance, poiMap);
                            if (isClique) {
                                nPlusCoList.add(starInstance);
                            }
                        }
                    }
                }
            }
            nPlusCoMap.put(nPlusCoType, nPlusCoList);
        }
    }

    public static Map<String, List<String>> genAllNPlusCo(Map<String, List<String>> oneCoMap, Map<String, List<String>> twoCoMap,Map<String, POI> poiMap,
                                                          Map<String, Map<String, String>> neighborMap, Map<String, List<String>> nCoMap) {

        int jie = 0;
        String method = "less";
        Map<String, List<String>> nPlusCoMap = new HashMap<>();
        for (String starTypeStr : nCoMap.keySet()) {
            if (jie == 0) jie = starTypeStr.split(",").length;
            int index1 = starTypeStr.lastIndexOf(",");
            String pre1 = starTypeStr.substring(0, index1), suf1 = starTypeStr.substring(index1 + 1);

            for (String neighborTypeStr : nCoMap.keySet()) {
                int index2 = neighborTypeStr.lastIndexOf(",");
                String pre2 = neighborTypeStr.substring(0, index2), suf2 = neighborTypeStr.substring(index2 + 1);
                String suf = suf1 + "," + suf2;
                if (pre1.equals(pre2) && Integer.valueOf(suf1) < Integer.valueOf(suf2)) { //前n-1阶相同，最后一个特征 左边比右边小
                    if (twoCoMap.get(suf) == null) {
                        continue; //末尾2阶模式不成立，则n+1阶模式不成立
                    }
                    String nPlusCoType = starTypeStr + "," + suf2;
                    if (method.equals("less")){ //根据星型邻居生成n+1阶模式行实例
                        genNPlusCo(oneCoMap, poiMap, neighborMap, nPlusCoMap, nPlusCoType); //这里使用星型邻居，没有利用n阶行实例
                    }
                    if (method.equals("base")){//根据n阶行实例生成n+1阶模式行实例 => 这个很慢
                        List<String> coList = new LinkedList<>();
                        List<String> list1 = nCoMap.get(starTypeStr);
                        List<String> list2 = nCoMap.get(neighborTypeStr);
                        for (String starInstance:list1) {
                            int index11 = starInstance.lastIndexOf(",");
                            String pre11 = starInstance.substring(0, index11), suf11 = starInstance.substring(index11 + 1);
                            for (String neigborInstance:list2){
                                int index22 = neigborInstance.lastIndexOf(",");
                                String pre22 = neigborInstance.substring(0, index22), suf22 = neigborInstance.substring(index22 + 1);
                                if (pre11.equals(pre22)) { //前n-1阶点实例相同
                                    POI poi11 = poiMap.get(suf11);
                                    POI poi22 = poiMap.get(suf22);
                                    if (Util.getDistance(poi11.getX(), poi11.getY(), poi22.getX(), poi22.getY()) <= Properties.R) {
                                        String n_co = pre11 + "," + suf11 + "," + suf22;
                                        coList.add(n_co);
                                    }
                                }
                            }
                        }
                        nPlusCoMap.put(nPlusCoType,coList);
                    }

                }
            }
        }

        if (nPlusCoMap.size() == 0) {
            System.out.println("==========================保存Joinless算法结果==========================");
            try (BufferedWriter bw = new BufferedWriter(new FileWriter(CoLocation.resultPath))) {
                bw.write(nCoMap.toString());
                bw.newLine(); //换行用

            } catch (Exception e) {
                System.out.println("保存结果 数据异常！！！");
                e.printStackTrace();
            }
        }
        return nPlusCoMap;
    }
}
